<template>
  <div
    v-show="$attrs.mode === globalKey || signal.split('-')[0] === $attrs.mode"
    class="preview-scan-column"
    @mouseenter="(focus=true)"
    @mouseleave="(focus=false)"
  >
    <header-ai
      ref="headerAi"
      :scale="pageImage.scale"
      :column="column"
      :pages="imgList.length"
      :show-clip="showClip"
      :signal="signal"
      v-bind="$attrs"
      @emitSetter="emitGetter"
    />
    <div
      :ref="`box-main-${signal}`"
      v-loading="imgLoading"
      class="box-main"
      :style="boxStyle"
    >
      <div
        v-if="(clipStatus!==0)"
        class="mask-control"
      >
        <span>截图</span>
        <div class="control-area">
          <div class="control-right">
            <span @click="clipPhoto(1)">完成</span>
            <span @click="clipPhoto(2)">关闭</span>
          </div>
        </div>
      </div>
      <div
        :id="`preview-box-${signal}`"
        class="preview-box"
        :style="{backgroundImage: 'url('+ previewImg.src + ')'}"
        @wheel.prevent="(e) => scaleDom(e, 'wheel')"
      >
        <clip-paint
          v-if="clipStatus!==0"
          ref="clipPaint"
          :detail.sync="clipList"
          :src="previewImg.src"
          :page-image="pageImage"
          :clip-status="clipStatus"
          @paintFinish="paintFinish"
        />
      </div>
      <div
        v-show="clipStatus===0"
        class="left-arrow arrow"
        @click="prevPage"
      >
        <i
          class="el-icon-arrow-left"
        />
      </div>
      <div
        v-show="clipStatus===0"
        class="right-arrow arrow"
        @click="nextPage"
      >
        <i
          class="el-icon-arrow-right"
        />
      </div>
    </div>
  </div>
</template>
<script>
import HeaderAi from './components/header.vue';
import ClipPaint from './components/clipPaint.vue';
import {
  CLIPSTATUS
} from './data/constant';
import {
  ENUMGETTERDISPATCH
} from './data/data';
import EXIF from 'exif-js';

export default {
  name: 'PreviewScanColumn',
  components: {
    HeaderAi,
    ClipPaint,
  },
  inheritAttrs: false,
  props: {
    /**
     * 图片数据
     */
    imgList: {
      type: Array,
      default: () => [],
    },
    /**
     * 分栏列表（只有第一栏才会显示顶部分栏数据）
     */
    column: {
      type: Array,
      default: () => [],
    },
    /**
     * 是否展示截图
     */
    showClip: {
      type: Boolean,
      default: false,
    },
    signal: {
      type: String,
      default: '',
    }
  },
  data() {
    return {
      focus: false, // 当前preview-scan-column组件是否聚焦
      clipList: { // 截图模式时专门存放截图数据的数组，长度只会为1
        startX: -99999,
        startY: 0,
        width: 0,
        height: 0,
      },
      finallyFile: null, // 截图的图像文件
      finallyBase64: null, // 截图的base64文件
      clipStatus: 0, // 是否处于截图状态 0 => 否， 1 => 是（已就绪）,2 => 是（正在截图）, 3 => 是（完成截图）
      previewImg: {
        target: null,
        src: '',
      }, // 当前底图图片
      pageImage: {
        imgScale: 0, // canvas实际默认为1
        minImgScale: 0, // canvas实际默认为1最小为1
        maxImgScale: 0, // canvas实际默认为1最大为4
        unit: 0, // 实际宽高和原始宽高中间的转换比例单位
        scale: 100, // 用于页面上展示放大缩小比例
        origin: {}, // 图片原始宽高
      },
      imgLoading: false, // 图片渲染加载中
      scaleConfig: { // 缩放比例参数
        maxLevel: 4,
        minLevel: 1
      }
    };
  },
  computed: {
    boxStyle() {
      return { 
        'border-style': (this.clipStatus !== 0 ? 'solid' : 'none'),
        'border-width': (this.clipStatus !== 0 ? '0 2px 2px' : '0'),
        'border-color': '#F59A23'
      };
    },
    globalKey() {
      return this.column.find(item => item.global)?.key ?? '';
    },
  },
  watch: {
    /**
     * 截图状态
     */
    clipStatus(val) {
      if (val === CLIPSTATUS.READY) { // 截图状态为是（已就绪）
        this.$nextTick(() => {
          this.cutClip(); // 截图dom mouse事件
        });
      } else if (val === CLIPSTATUS.NO) { // 截图状态为否
        document.oncontextmenu = null; // 取消拦截右键
        this.imgDraggableEvent(true); // 底图拖动事件
      }
    },
    '$attrs.mode': {
      handler(val) {
        if (val === this.globalKey || val === this.signal.split('-')[0]) {
          this.calcImage();
        }
      },
    },
    showClip: {
      handler(val) {
        this.eventInit(val); // 键盘相关事件监听
      },
      immediate: true,
    },
    imgList: {
      handler(val) {
        val.length && this.updateImage();
      },
      deep: true,
    },
  },
  beforeDestroy() {
    this.eventInit(false); // 取消键盘事件监听
    this.imgDraggableEvent(false); // 取消截图模式dom监听
  },
  mounted() {
    this.initConfig();
  },
  methods: {
    /**
     * 初始化配置方法
     */
    initConfig() {
      this.$nextTick(() => {
        this.previewImg.target = document.getElementById(`preview-box-${this.signal}`);
        this.updateImage();
        this.imgDraggableEvent(true);
      });
    },
    /**
     * 切图模式-底图拖动事件
     * @param {*} type
     */
    imgDraggableEvent(type) {
      this.$nextTick(() => {
        const dom = this.previewImg.target;
        if (type) {
          this.previewImg.target.style.cursor = 'move';
          dom.onmousedown = (e) => {
            // 鼠标按下，计算当前元素距离可视区的距离
            const disX = e.clientX - dom.offsetLeft;
            const disY = e.clientY - dom.offsetTop;
            document.onmousemove = (e) => {
              // 计算移动的距离
              const l = e.clientX - disX; // 阻止越界
              const t = e.clientY - disY;
              dom.style.left = l + 'px';
              dom.style.top = t + 'px';
            };
            dom.onmouseleave = () => {
              document.onmousemove = null;
              dom.onmouseup = null;
            };
            dom.onmouseup = () => {
              document.onmousemove = null;
              dom.onmouseup = null;
            };
            return false;
          };
        } else {
          dom.onmousedown = null;
          document.onmousemove = null;
          dom.onmouseup = null;
        }
      });
    },
    /**
     * 当前缩放比例控制
     * @param {*} val
     */
    scaleControl(val) {
      if (this.clipStatus !== CLIPSTATUS.NO) { // 当截图模式下正在截图时禁止底图缩放
        return;
      }
      // 图像比例放大缩小时触发
      if (val) {
        this.scaleDom(val, 'click');
      // 图像想要重置原100%比例 && 图像当前非原100%比例 => 重新计算图像显示比例并且让图像居中
      } else if (!val && this.pageImage.scale !== 100) {
        this.resetOriginScale();
      // 图像想要重置原100%比例 并且当前处于100%比例 => 让图像居中
      } else {
        const stylesList = new Map();
        this.setImageToCenter(stylesList);
        for (const [k, v] of stylesList) {
          this.setStyle(this.previewImg.target, k, v);
        }
      }
    },
    /**
     * 重置图像原始比例宽高
     */
    async resetOriginScale() {
      this.pageImage.scale = 100;
      try {
        this.resetOriginStyles();
      } catch(e) {
        this.$message.error(e.message);
      }
    },
    /**
     * 批量重置图像样式
     * 让重置后的像素以100%展示在可视区域最中心位置
     */
    resetOriginStyles() {
      const stylesList = new Map();
      const width = this.pageImage.origin.w * this.pageImage.imgScale + 'px';
      stylesList.set('width', width);
      const height = this.pageImage.origin.h * this.pageImage.imgScale + 'px';
      stylesList.set('height', height);
      this.setImageToCenter(stylesList);
      for (const [k, v] of stylesList) {
        this.setStyle(this.previewImg.target, k, v);
      }
    },
    /**
     * 让图像居中
     * @param {*} stylesList 样式Map对象
     */
    setImageToCenter(stylesList) {
      const left = '50%';
      stylesList.set('left', left);
      const top = '50%';
      stylesList.set('top', top);
      const translate = '-50%, -50%';
      stylesList.set('translate', translate);
    },
    /**
     * 给对应dom设置样式属性
     * @param {*} target
     * @param {*} prop
     * @param {*} value
     */
    setStyle(modifyTarget, prop, value) {
      modifyTarget.style[prop] = value;
    },
    /**
     * 鼠标滑轮事件
     * 用于计算当前图像的应该展示的宽高
     * @param {*} e
     */
    scaleDom(e, type = 'wheel') {
      if (this.clipStatus !== CLIPSTATUS.NO) { // 当截图模式下正在截图时禁止底图缩放
        return;
      }
      let nowPanel = this.pageImage.scale / 100; // 目前放大缩放的倍数
      // 一般在谷歌滚动滑轮上下分别是 120 / -120
      const size = type === 'wheel' ? e.wheelDelta / 1200 : parseFloat(e / 10);
      nowPanel += size;
      // 不能直接取 0.1， 因为浏览器不同，可能每次size都不能刚好为 0.1，可能有误差
      const minScaleRange = this.scaleConfig.minLevel - 0.02;
      const maxScaleRange = this.scaleConfig.maxLevel + 0.02;
      // 当缩放比例在当前允许的范围内时，才能进行缩放
      if (nowPanel >= minScaleRange && nowPanel <= maxScaleRange) {
        this.pageImage.scale = Number((nowPanel * 100).toFixed(0));
        this.setImageWH(nowPanel);
      }
    },
    /**
     * 获取图片原始dpi
     * @param file
     */
    getOriginalDPI(file) {
      return new Promise((resolve) => {
        EXIF.getData(file, function() {
          let dpiX = 72; // 默认 72 DPI
          let dpiY = 72; // 默认 72 DPI
          dpiX = EXIF.getTag(this, 'XResolution');
          dpiY = EXIF.getTag(this, 'YResolution');
          // 没有dpi的情况下
          if (!dpiX) {
            // 常见DPI值映射表
            const dpiMap = [
              { maxPixels: 640, dpi: 72 },    // 小尺寸通常72dpi
              { maxPixels: 1920, dpi: 96 },   // 中等尺寸96dpi
              { maxPixels: Infinity, dpi: 300 }, // 大尺寸300dpi
            ];
            const maxDimension = Math.max(file.width, file.height);
            dpiX = dpiY = dpiMap.find(item => maxDimension <= item.maxPixels).dpi;
          }
          resolve({ dpiX, dpiY });
        });
      });
    },
    /**
     * 图像加载时 onload触发后计算下列参数字段，为图像缩放展示的核心逻辑
     * pageImage.scale： 计算图像的缩放比例 以100为基准（用于页面上展示，可修改）
     * 让图像能以最好的角度完整的展示的可视区域内
     * pageImage.imgScale： 图像原始宽高与可视区域宽高比后的最佳展示比例（以宽，高中最小的比例为主，不允许修改！！！）
     * pageImage.maxImgScale：最大缩放倍数，是imgScale倍速（不允许修改！！！）
     * pageImage.minImgScale：最小缩放倍数，是imgScale倍速（不允许修改！！！）
     * pageImage.origin：w/h 初始像素宽高（不允许修改！！！）
     * pageImage.unit: 实际宽高和原始宽高中间的转换比例单位（不允许修改！！！）
     */
    calcImage() {
      if (!this.previewImg.src) return;
      const image = new Image();
      this.imgLoading = true;
      return new Promise((resolve, reject) => {
        image.src = this.previewImg.src;
        image.onload = async () => {
          const { dpiX, dpiY } = await this.getOriginalDPI(image);
          // 记录当前图像的原始宽高
          this.pageImage.origin = {
            w: image.width,
            h: image.height,
            xDpi: dpiX,
            yDpi: dpiY,
          };
          const parent = this.$refs[`box-main-${this.signal}`];
          const panel = this.pageImage.scale / 100; // 放大倍数
          // 计算在当前可视区域内的图像比例参数
          this.calcImgScale(parent);
          // 设置当前缩放比例下的图像展示宽高
          this.setImageWH(panel);
          this.imgLoading = false;
          resolve(true);
        };
        image.onerror = () => {
          const err = new Error('图像加载失败');
          reject(err);
        };
      });
    },
    /**
     * 计算在当前可视区域内的图像比例参数
     * @param {*} parent 可视区域的dom
     */
    calcImgScale(parent) {
      if (!parent) return;
      // 重新计算当前可视区域内的最佳展示比例
      this.pageImage.imgScale = Number((Math.min(parent.offsetWidth / this.pageImage.origin.w, parent.offsetHeight / this.pageImage.origin.h)).toFixed(6));
      this.pageImage.unit = 100 / Number(this.pageImage.imgScale.toFixed(6)); // 转换比例生成 保存4位小数 更精确
      // 最大放大4倍比例
      this.pageImage.maxImgScale = Number((this.pageImage.imgScale * this.scaleConfig.maxLevel).toFixed(6));
      // 最小放大1倍比例
      this.pageImage.minImgScale = Number((this.pageImage.imgScale * this.scaleConfig.minLevel).toFixed(6));
    },
    /**
     * 设置当前缩放比例下的图像展示宽高
     * @param {*} panel 缩放倍数
     */
    setImageWH(panel) {
      this.previewImg.target.style.width = this.pageImage.origin.w * this.pageImage.imgScale * panel + 'px';
      this.previewImg.target.style.height = this.pageImage.origin.h * this.pageImage.imgScale * panel + 'px';
    },
    /**
     * 更换图片url
     */
    updateImageUrl() {
      // 当前没有图片时，清空现有的图像相关数据
      if (!this.imgList.length) {
        this.previewImg.src = '';
        return;
      }
      this.imgLoading = true;
      this.$nextTick(() => {
        const curPage = this.$refs['headerAi']?.curPage ?? 1;
        const flag = this.imgList.find((_item, index) => (index + 1) === curPage);
        if (flag) {
          this.previewImg.src = flag.filePath;
        } else {
          this.previewImg.src = this.imgList[0].filePath;
        }
      });
    },
    /**
     * 键盘事件，屏幕失焦事件监听
     */
    eventInit(type) {
      this.$nextTick(() => {
        if (type) {
          document.addEventListener('keydown', this.screensEvent);
          document.addEventListener('keyup', this.keyupEvent);
          document.onblur = () => {
            this.mouseCtrl = false;
          };
        } else {
          document.removeEventListener('keydown', this.screensEvent);
          document.removeEventListener('keyup', this.keyupEvent);
          document.onblur = null;
        }
      });
    },
    /**
     * 监听键盘抬起 目前只监听ctrl键
     * @param {*} e
     */
    keyupEvent() {
      this.mouseCtrl = false;
    },
    /**
     * 截图快捷键
     * @param {*} e
     */
    screensEvent(e) {
      if (this.focus) { // 在当前组件内时，截图快捷键生效
        if ((e.altKey || e.ctrlKey) && [81, 83, 87, 90].includes(e.keyCode)) {
          e.preventDefault();
          let typeCode = '';
          if (e.altKey && e.keyCode === 87) typeCode = 0; // 开始截图：alt + w
          else if (e.ctrlKey && e.keyCode === 83) typeCode = 1; // 保存当前截图：ctrl + s
          else if (e.ctrlKey && e.keyCode === 81) typeCode = 2; // 关闭当前截图：ctrl + q
          else if (e.ctrlKey && e.keyCode === 90) typeCode = 3; // 撤销当前截图操作：ctrl + z
          if (typeCode || typeCode === 0) this.clipPhoto(typeCode);
        }
        return false;
      }
    },
    /**
     * 开始截图
     */
    startClip() {
      this.clipStatus = CLIPSTATUS.READY;
      this.$nextTick(() => {
        document.oncontextmenu = e => { // 阻止右键默认事件
          e.preventDefault();
        };
      });
    },
    /**
     * 截图模式-截图mouse事件
     */
    cutClip() {
      const dom = this.previewImg.target;
      dom.style.cursor = 'crosshair';
      this.clipList = {
        startX: -99999,
        startY: 0,
        width: 0,
        height: 0
      };
      dom.onmousedown = e => {
        let left = 0;
        let top = 0;
        let drag = false;
        const origin = dom.getBoundingClientRect();
        if (this.clipStatus === CLIPSTATUS.READY) {
          left = e.clientX - origin.x + 1;
          top = e.clientY - origin.y + 1;
          this.clipList.startX = left / this.pageImage.imgScale;
          this.clipList.startY = top / this.pageImage.imgScale;
          this.$refs.clipPaint.clearAll();
        }
        dom.onmousemove = e => {
          if (this.clipStatus === CLIPSTATUS.READY || this.clipStatus === CLIPSTATUS.ING) {
            this.clipStatus = CLIPSTATUS.ING;
            const width = e.clientX - origin.x - 1 - left;
            const height = e.clientY - origin.y - 1 - top;
            this.clipList.width = width / this.pageImage.imgScale;
            this.clipList.height = height / this.pageImage.imgScale;
          }
          drag = true;
        };
        // 针对绘制矩形框时，鼠标不在当前dom范围内，则直接进入 截图完成状态
        dom.onmouseleave = () => {
          if (this.clipStatus === CLIPSTATUS.ING) {
            dom.onmousemove = null;
            dom.onmouseup = null;
            this.clipStatus = CLIPSTATUS.FINISH;
            dom.onmouseleave = null;
          }
        };
        dom.onmouseup = e => {
          if (this.clipStatus === CLIPSTATUS.ING) {
            if (drag) {
              dom.onmousemove = null;
              dom.onmouseup = null;
              this.clipStatus = CLIPSTATUS.FINISH;
            }
          } else if (this.clipStatus === CLIPSTATUS.FINISH) {
            if (!drag) {
              dom.onmousemove = null;
              if (e.target.tagName === 'SPAN') {
                dom.onmouseup = null;
                return;
              }
              if (this.finallyFile) {
                dom.onmouseup = null;
                this.saveClip();
              }
            }
          }
        };
      };
    },
    /**
     * 截图操作 0 => 开始截图 1 => 保存截图 2 => 退出截图 3 => 取消当次截图
     * @param {*} type
     */
    clipPhoto(type) {
      switch (type) {
        case 0:
          this.startClip();
          break;
        case 1:
          this.saveClip();
          break;
        case 2:
          this.closeClip();
          break;
        case 3:
          this.cancelClip();
          break;
      }
    },
    /**
     * 取消截图（不退出截图状态，只是把当前截图动作及结果取消）
     */
    cancelClip() {
      if (this.clipStatus !== CLIPSTATUS.FINISH) return; // 如果当前状态不是完成截图状态时，不执行后续
      this.clipStatus = CLIPSTATUS.READY; // 将状态置回就绪
      this.finallyFile = null;
      this.clipList.startX = -99999; // 重置坐标点
      this.clipList.startY = 0;
      this.clipList.width = 0;
      this.clipList.height = 0;
    },
    /**
     * 退出截图（退出截图状态，清空所有截图数据）
     */
    closeClip() {
      this.cancelClip();
      this.clipStatus = CLIPSTATUS.NO;
      this.previewImg.target.style.cursor = 'move';
      setTimeout(() => { // 必须要加延时，不然右键退出截图状态时会立即恢复默认事件
        document.oncontextmenu = null; // 恢复右键默认事件
      }, 100);
    },
    /**
     * 没有截图数据时的处理
     */
    noDataTip() {
      return new Promise((resolve, reject) => {
        if (!this.finallyFile) {
          reject('无截图数据');
        } else {
          resolve();
        }
      });
    },
    /**
     * 保存截图（前置方法）
     */
    saveClip() {
      // 画笔是否处于默认状态
      const curPenIsDefault = () => this.$refs.clipPaint.curPen === -1;
      this.previewImg.target.onmousemove = null; // 清除onmousedown事件
      if (curPenIsDefault()) {
        this.saveClipControl();
        return;
      }
      const msg = this.$message.warning('正在生成截图中,请稍后');
      // 定时重置画笔逻辑
      const repeatGetResult = setInterval(() => {
        this.$refs.clipPaint.clearRect();
        if (curPenIsDefault()) {
          clearInterval(repeatGetResult); // 停止计时
          msg.close(); // 关闭弹框
          this.saveClipControl();
        }
      }, 1000);
    },
    /**
     * 保存截图（退出截图状态，保存所有截图数据）=> 后置方法
     */
    saveClipControl() {
      this.noDataTip().then(() => {
        const fileObj = {
          file: this.finallyFile,
          base64: this.finallyBase64
        };
        this.$emit('clipSuccess', fileObj);
        this.closeClip();
        this.$message.success('截图成功！');
      });
    },
    /**
     * 涂鸦截图文件回调
     * @param {*} file
     */
    paintFinish(file) {
      this.finallyFile = file.file;
      this.finallyBase64 = file.base64;
    },
    /**
     * 事件接收器
     * @param {*} type 事件类型
     * @param {*} item 事件值
     */
    emitGetter(item) {
      const { type, value } = item;
      ENUMGETTERDISPATCH[type](this, value);
    },
    /**
     * 全局点击逻辑
     */
    globalClick() {
      if (this.finallyFile) { // 截图完成后，点击左键保存截图（只作用于截图模式）
        this.clipPhoto(1);
      }
    },
    modeChange(value) {
      this.$emit('modeChange', value);
    },
    /**
     * 上一页
     */
    prevPage() {
      // 处于截图状态，则切换页码无效
      if (this.clipStatus!==CLIPSTATUS.NO) return;
      if(this.$refs['headerAi'].curPage > 1) {
        this.$refs['headerAi'].curPage--;
      }
    },
    /**
     * 下一页
     */
    nextPage() {
      // 处于截图状态，则切换页码无效
      if (this.clipStatus!==CLIPSTATUS.NO) return;
      if (this.$refs['headerAi'].curPage < this.imgList.length) {
        this.$refs['headerAi'].curPage++;
      }
    },
    /**
     * 更新图片路径并重新计算布局
     */
    async updateImage() {
      if (this.clipStatus!==CLIPSTATUS.NO) {
        this.$message.warning('当前处于截图状态，无法更新视图');
        return;
      }
      await this.updateImageUrl();
      await this.calcImage();
      await this.pageNoChange();
    },
    /**
     * 图片当前页码改变
     */
    pageNoChange() {
      const curColumn = {};
      curColumn.key = this.$refs['headerAi'].signal.split('-')[0];
      curColumn.pageNo = this.$refs['headerAi'].curPage;
      this.$emit('pageNoChange', curColumn);
    }
  },
};
</script>
<style lang="scss" scoped>
.preview-scan-column {
  width: 100%;
  height: 100%;
  overflow: auto;
  background-color: #7F7F7F;
  color: #fff;
  position: relative;
  display: flex;
  flex-flow: column nowrap;
  box-shadow: 1px 1px 5px 0px;
  .box-main {
    flex: auto;
    position: relative;
    overflow: hidden;
    .mask-control{
      width: 100%;
      height: 18px;
      display: flex;
      flex-flow: row nowrap;
      align-items: center;
      justify-content: space-between;
      background-color: #F59A23;
      position: absolute;
      left: 0;
      top: 0;
      z-index: 9;
      user-select: none;
      span {
        padding: 0 10px;
        line-height: 18px;
      }
      .control-area {
        display: flex;
        flex-flow: row nowrap;
        align-items: center;
        .control-button {
          margin-right: 20px;
          span {
            color: #fff;
            cursor: pointer;
          }
        }
        .control-right{
          display: flex;
          flex-flow: row nowrap;
          align-items: center;
          span {
            cursor: pointer;
            &:first-child {
              border-right: 1px solid #fff;
            }
          }
        }
      }
    }
    .preview-box{
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
      background-size: 100%;
      background-repeat: no-repeat;
    }
    .arrow {
      width: 40px;
      height: 40px;
      text-align: center;
      line-height: 40px;
      border-radius: 50%;
      position: absolute;
      cursor: pointer;
      &:hover {
        background-color: #eee;
        color: #ffae0d;
      }
      &.left-arrow {
        top: 50%;
        left: 20px;
      }
      &.right-arrow {
        top: 50%;
        right: 20px;
      }
    }
  }
}
</style>
